import abc
import dataclasses
import importlib

from zkl_serialization import Serializable, ValueNote, get_root_convertor


class TypedSerializable(Serializable, abc.ABC):
    def serialize(self) -> dict:
        return {
            "type": self.serialize_type(),
            **self.serialize_content()
        }

    @classmethod
    def deserialize(cls, data: dict):
        typ_str = data.get("type")
        if typ_str is None:
            return cls.deserialize_content(data)

        typ = cls.deserialize_type(typ_str)
        assert issubclass(typ, cls)

        data = {**data}
        data.pop("type")

        root_convertor = get_root_convertor()
        with ValueNote(target_type_hint=typ):
            return root_convertor(data)

    def serialize_type(self) -> str:
        typ = type(self)
        typ_module = typ.__module__
        typ_qualname = typ.__qualname__
        return f"{typ_module}:{typ_qualname}"

    @classmethod
    def deserialize_type(cls, typ_str: str):
        typ_module, typ_qualname = typ_str.split(":")
        module = importlib.import_module(typ_module)
        typ = module
        for name in typ_qualname.split("."):
            typ = getattr(typ, name)
        return typ

    @abc.abstractmethod
    def serialize_content(self) -> dict:
        pass

    @classmethod
    @abc.abstractmethod
    def deserialize_content(cls, data: dict):
        pass


class TypedConfig(TypedSerializable, abc.ABC):
    # typing

    def serialize_type(self) -> str:
        typ_str = super().serialize_type()
        assert typ_str.endswith("Config")
        return typ_str[:-len("Config")]

    @classmethod
    def deserialize_type(cls, typ_str: str):
        typ_str += "Config"
        return super().deserialize_type(typ_str)

    # content

    def serialize_content(self):
        self._check_is_dataclass()
        raise NotImplementedError  # drop down to use dataclass serialization

    @classmethod
    def deserialize_content(cls, data: dict):
        cls._check_is_dataclass()
        raise NotImplementedError  # drop down to use dataclass serialization

    # utils

    @classmethod
    def _check_is_dataclass(cls):
        if dataclasses.is_dataclass(cls):
            return
        raise TypeError(
            f"{cls} is not a dataclass! "
            f"You must override method 'serialize_content' and 'deserialize_content'!")
